module gpu_bus_matrix_fifo_s #
(
parameter  DWIDTH = 32,
parameter  DSIZE  = 4
)
(
    input                     clk,
    input                     rst_n,
    input                     wr,
    input                     rd,
    input      [DWIDTH-1:0]   datain,
    output reg [DWIDTH-1:0]   dataout, 
    output                    full,
    output reg                emty,
    output reg                almost_empty,
    output                    almost_full
); 


localparam  AFULL  = 2**DSIZE - 1'b1;
localparam  FULLSIZE = 2**DSIZE; 


reg          afull_in;
reg          full_in;
reg          empty_in;
reg [DSIZE-1:0] rp, wp;
reg [DSIZE:0] datacnt;  
reg  [1:0]   cur_state;
reg  [1:0]   next_state;
reg [DWIDTH-1:0] dataout_in;

wire  rd_in,wr_in;
wire                  Load;  


reg  [DWIDTH-1:0] mem[AFULL:0];
always@(posedge clk)
if(wr_in)  mem[wp]<=  datain;

always@(posedge clk)
if(rd_in)  dataout_in<=  mem[rp];  



assign #1 full = full_in;
assign #1 almost_full = afull_in;
assign #1 wr_in = wr;
// memory write pointer increment
always@(posedge clk or negedge rst_n) begin
    if(!rst_n) wp<=  0;
    else begin
      if(wr_in && ~full_in) wp<=  wp+1'b1;
    end
end
// memory read pointer increment
always@(posedge clk or negedge rst_n)begin
    if(!rst_n) rp <=  0;
    else begin
      if(rd_in && ~empty_in) rp <=  rp + 1'b1;
    end
end

// fifo data remain count
always @(posedge clk or negedge rst_n)begin
    if(!rst_n) datacnt <= 0;
    else case({rd_in,wr_in})
        2'b00: datacnt <= datacnt;
        2'b01: if(datacnt != FULLSIZE ) datacnt <= datacnt + 1'b1;
        2'b10: if(datacnt != {DSIZE+1{1'b0}} ) datacnt <= datacnt - 1'b1;
        2'b11: datacnt <= datacnt;
    endcase
end        

// Full signal generate

always@(posedge clk or negedge rst_n) begin
    if(!rst_n) full_in <=  1'b1;
    else begin
      if( (~rd_in && wr_in)&&(datacnt == FULLSIZE -1'b1)) full_in <=  1'b1;
      else if((datacnt == FULLSIZE) && (rd_in && ~wr_in)) full_in <= 1'b0;
      else if(datacnt!= FULLSIZE) full_in <= 1'b0;  
    end
end


always@(posedge clk or negedge rst_n) begin
    if(!rst_n) afull_in <= 1'b1;
    else begin
      if((datacnt == AFULL-2'd1) && (~rd_in && wr_in)) afull_in <= 1'b1;
      else if ((datacnt == AFULL ) && (rd_in && ~wr_in)) afull_in <= 1'b0;
      else if (datacnt < AFULL ) afull_in <= 1'b0;   
    end
end    

// Empty signal generate
always@(posedge clk or negedge rst_n) begin
    if(!rst_n) empty_in <=  1'b1;
    else begin
      if((rd_in&&~wr_in)&&((rp==wp-1) || (rp=={DSIZE{1'b1}}&&wp==4'h0)))
        empty_in<=  1'b1;
      else if(empty_in && wr_in) empty_in<=  1'b0;
    end
end


//******************** Fall-Through STATE*************
//____________________________________________________ cur_state


parameter     IDLE=2'h0,FRST=2'h1,ONLO=2'h2,HVTW=2'h3;

always@(posedge clk or negedge rst_n)
if(!rst_n)              cur_state<=  IDLE;
else                  cur_state<=  next_state;

always@(*)
  begin
    case(cur_state)
      IDLE:
            if(!empty_in)  next_state = FRST;
            else            next_state = IDLE;            
      FRST:                            
            if( empty_in)  next_state = ONLO;
            else            next_state = HVTW;            
      ONLO:                            
            if( empty_in & rd )     
                            next_state = IDLE;
            else if(!empty_in & rd)
                            next_state = FRST;
            else if(!empty_in &!rd)
                            next_state = HVTW;
            else            next_state = ONLO;            
      HVTW:                        
            if( empty_in & rd)  
                            next_state = ONLO;
            else            next_state = HVTW;
    default:                next_state = IDLE;
    endcase
  end

//____________________________________________________ rd_in
assign   rd_in  =(cur_state == IDLE |cur_state == FRST  |
                   cur_state == ONLO |((cur_state == HVTW) &rd)) &
                  !empty_in;
    
//____________________________________________________ Load
assign  Load=cur_state == FRST | ((cur_state == HVTW)&rd);

//____________________________________________________ dataout
always@(posedge clk or negedge rst_n)
if(!rst_n)                dataout<=  {DWIDTH{1'b1}};
else if(Load)           dataout<=  dataout_in;

//____________________________________________________ empty
always@(posedge clk or negedge rst_n)
if(!rst_n)              emty<=  1'b1;
else if(Load)         emty<=  1'b0;
else if((cur_state == ONLO) & rd)
                      emty<=  1'b1;
//____________________________________________________ almost_empty
always@(posedge clk or negedge rst_n)
if(!rst_n)              almost_empty<=  1'b1;
else if(((cur_state == ONLO) &!empty_in &!rd) |
        ((cur_state == FRST) &!empty_in &!rd) )almost_empty<=  1'b0;
else if((cur_state == HVTW) & empty_in & rd )almost_empty<=  1'b1;

wire  emrd/*synthesis syn_keep=1*/;
wire  fuwr/*synthesis syn_keep=1*/;
assign  emrd = emty && rd;
assign  fuwr = full && wr;


always @(posedge clk)
begin
    if(emty && rd) 
    begin
        $display("%m, fifo empty read.\n");
   // #1000;
   //     $stop;
    end
    if(full && wr)
    begin
        $display("%m, fifo full write.\n");
    //    #1000;
    //    $stop;
 
    end
end

endmodule
